<?php
/** Copyright 2014 TinPhp Systems, Inc.
 * @author zhangxilian 2013-8-20
 * @package TinPhp
 */
/** XML处理类
 * 应用：
$site = new Xml("tables", "data/demo.xml");
$site_tsg2 = array();
$site_tsg2["bg"] = $site->getRecordById(1);
$site_tsg2["wj"] = $site->getRecordById(2);

xml文档：
<?xml version="1.0" encoding="UTF-8"?>
<tables>
<record>
<key>1</key>
<title>电子图书</title>
<url></url>
</record>
<record>
<key>2</key>
<title>电子期刊</title>
<url></url>
</record>
</tables> */
class Xml {

	private $fpath;
	private $enter;
	private $root;

	function __construct($root, $fpath) {
		$this->fpath = $fpath;
		$this->root = $root;
		$this->enter = chr(13) . chr(10);
		$this->checkFile();
	}

	/**
	 * 函数名:insert
	 * 说明:插入一条记录
	 */
	public function insert($fields) {
		$content = $this->getFileContent();
		preg_match_all('|<key>(\d+?)<\/key>|', $content, $matches);
		rsort($matches[1]);
		$newkey = $matches[1][0] + 1;

		$record = '<record>' . $this->enter;
		$record .= '<key>' . $newkey . '</key>' . $this->enter;
		foreach ($fields as $k => $v) {
			$record .= "<$k>$v</$k>" . $this->enter;
		}
		$record .= '</record>' . $this->enter . $this->enter;
		$this->save(preg_replace('/(?=<\/' . $this->root . '>)/', $record, $content));
		return $newkey;
	}

	/**
	 * 函数名:insertM
	 * 说明:插入多条记录，参数类型必须是二维数组
	 */
	public function insertM($arr) {
		$content = $this->getFileContent();
		preg_match_all('|<key>(\d+?)<\/key>|', $content, $matches);
		rsort($matches[1]);
		$newkey = $matches[1][0] + 1;

		$record = '';
		for ($i = 0; $i < count($arr); $i++) {
			$record .= '<record>' . $this->enter;
			$record .= '<key>' . $newkey . '</key>' . $this->enter;
			foreach ($arr[$i] as $k => $v) {
				$record .= "<$k>$v</$k>" . $this->enter;
			}
			$record .= '</record>' . $this->enter . $this->enter;
			$newkey++;
		}
		$this->save(preg_replace('/(?=<\/' . $this->root . '>)/', $record, $content));
		return true;
	}

	/**
	 * 函数名:checkFile
	 * 说明:如果文件不存在，则创建之，并初始化
	 *     否则检查文件规则是否被破坏
	 */
	private function checkFile() {
		if (!file_exists($this->fpath)) {
			$xmlstr = '';
			$xmlstr = '<?xml version="1.0" encoding="UTF-8"?>' . $this->enter;
			$xmlstr .= '<' . $this->root . '>' . $this->enter . $this->enter;
			$xmlstr .= '</' . $this->root . '>';
			$this->save($xmlstr);
		} else {
			$content = $this->getFileContent();
			$bool_statement = preg_match('/<\?xml version="1\.0".*?\?>/', $content) == 0 ? false : true;
			$bool_root = preg_match('/<' . $this->root . '>.*<\/' . $this->root . '>/s', $content) == 0 ? false : true;
			preg_match_all('|(<record>(?:.+?)<\/record>)|s', $content, $matches);
			for ($i = 0; $i < count($matches[1]); $i++) {
				$re = '/^<record>\s*<key>(\d+)<\/key>\s*[\s\S]*\s*<\/record>$/';
				$bool_record = preg_match($re, $matches[1][$i], $arr) == 0 ? false : true;
				$keys[] = $arr[1];
				if (!$bool_record)
					break;
			}
			if (!empty ($keys)) {
				rsort($keys);
			}
			$bool_record = true;
			$bool_repeat = true;
			//$bool_repeat=preg_match('/(\d),\1/',join(',',$keys),$wb)==0 ? true : false;
			if (!($bool_statement && $bool_root && $bool_record && $bool_repeat)) {
				echo '文件规则已被破坏';
				exit;
			}
		}
	}

	/**
	 * 函数名:getRecordById
	 * 说明:根据主键ID，获取字段信息
	 */
	public function getRecordById($id) {
		$content = $this->getFileContent();
		preg_match('/<record>(\s*<key>' . $id . '<\/key>.+?)<\/record>/s', $content, $matches);
		preg_match_all('|(<.+</.+>)|', $matches[1], $arr);
		$row = array ();
		$arrstr = '';
		for ($i = 0; $i < count($arr[1]); $i++) {
			preg_match('/^.+>(.+)<\/(.+)>/', $arr[1][$i], $farr);
			$arrstr .= "'$farr[2]'=>'$farr[1]'";
			if ($i < count($arr[1]) - 1)
				$arrstr .= ',';
		}
		eval ("\$row=array($arrstr);");
		return $row;
	}

	/**
	 * 函数名:updateRecordById
	 * 说明:根据主键ID，更新字段
	 */
	public function updateRecordById($id, $form_arr = array ()) {
		$content = $this->getFileContent();
		foreach ($form_arr as $k => $v) {
			$re = '/(<key>' . $id . '<\/key>[\s\S]*?<' . $k . '>).+?(<\/' . $k . '>)/s';
			$content = preg_replace($re, '${1}' . $v . '${2}', $content);
		}
		$this->save($content);
		return true;
	}

	public function deleteRecordById($id) {
		$content = $this->getFileContent();
		$content = preg_replace('/<record>\s*<key>' . $id . '+.+?<\/record>(\s)?/s', '', $content);
		$this->save($content);
		return true;
	}

	/**
	 * 函数名:getList
	 * 说明:获取所有记录，返回一个二维数组
	 *     先获取所有record记录，再获取字段名和值
	 */
	public function getList() {
		$content = $this->getFileContent();
		$rs = array ();
		preg_match_all('|<record>(.+?)<\/record>|s', $content, $matches);
		for ($i = 0; $i < count($matches[1]); $i++) {
			preg_match_all('|(<.+</.+>)|', $matches[1][$i], $tmparr);
			$arrstr = '';
			for ($j = 0; $j < count($tmparr[1]); $j++) {
				preg_match('/^.+>(.+)<\/(.+)>/', $tmparr[1][$j], $farr);
				$arrstr .= "'$farr[2]'=>'$farr[1]'";
				if ($j < count($tmparr[1]) - 1)
					$arrstr .= ',';
			}
			eval ("\$rs[]=array($arrstr);");
		}
		return $rs;
	}

	public function getFileContent() {
		$hd = fopen($this->fpath, 'r');
		$content = fread($hd, filesize($this->fpath));
		fclose($hd);
		return $content;
	}

	private function save($content) {
		$hd = fopen($this->fpath, 'w');
		fwrite($hd, $content);
		fclose($hd);
	}
//===================================================================	
	/**
	 * XML转数组
	 * @param string $contents xml文件内容
	 * @param int $get_attributes 属性默认为1 
	 * @param string $priority 标记
	 * @return type 
	 */
	public static function xml2array($contents, $get_attributes = 1, $priority = 'tag') {
		if (!$contents)
			return array ();

		if (!function_exists('xml_parser_create')) {
			//print "'xml_parser_create()' function not found!";
			return array ();
		}

		//Get the XML parser of PHP - PHP must have this module for the parser to work
		$parser = xml_parser_create('');
		xml_parser_set_option($parser, XML_OPTION_TARGET_ENCODING, "UTF-8"); # http://minutillo.com/steve/weblog/2004/6/17/php-xml-and-character-encodings-a-tale-of-sadness-rage-and-data-loss
		xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
		xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1);
		xml_parse_into_struct($parser, trim($contents), $xml_values);
		xml_parser_free($parser);

		if (!$xml_values)
			return; //Hmm...

		//Initializations
		$xml_array = array ();
		$parents = array ();
		$opened_tags = array ();
		$arr = array ();

		$current = & $xml_array; //Refference
		//Go through the tags.
		$repeated_tag_index = array (); //Multiple tags with same name will be turned into an array
		foreach ($xml_values as $data) {
			unset ($attributes, $value); //Remove existing values, or there will be trouble
			//This command will extract these variables into the foreach scope
			// tag(string), type(string), level(int), attributes(array).
			extract($data); //We could use the array by itself, but this cooler.

			$result = array ();
			$attributes_data = array ();

			if (isset ($value)) {
				if ($priority == 'tag')
					$result = $value;
				else
					$result['value'] = $value; //Put the value in a assoc array if we are in the 'Attribute' mode
			}

			//Set the attributes too.
			if (isset ($attributes) and $get_attributes) {
				foreach ($attributes as $attr => $val) {
					if ($priority == 'tag')
						$attributes_data[$attr] = $val;
					else
						$result['attr'][$attr] = $val; //Set all the attributes in a array called 'attr'
				}
			}
			//See tag status and do the needed.
			if ($type == "open") {
				//The starting of the tag '<tag>'
				$parent[$level -1] = & $current;
				if (!is_array($current) or (!in_array($tag, array_keys($current)))) { //Insert New tag
					$current[$tag] = $result;
					if ($attributes_data)
						$current[$tag . '_attr'] = $attributes_data;
					$repeated_tag_index[$tag . '_' . $level] = 1;

					$current = & $current[$tag];
				} else { //There was another element with the same tag name
					if (isset ($current[$tag][0])) {
						//If there is a 0th element it is already an array
						$current[$tag][$repeated_tag_index[$tag . '_' . $level]] = $result;
						$repeated_tag_index[$tag . '_' . $level]++;
					} else {
						//This section will make the value an array if multiple tags with the same name appear together
						$current[$tag] = array (
							$current[$tag],
							$result
						); //This will combine the existing item and the new item together to make an array
						$repeated_tag_index[$tag . '_' . $level] = 2;

						if (isset ($current[$tag . '_attr'])) { //The attribute of the last(0th) tag must be moved as well
							$current[$tag]['0_attr'] = $current[$tag . '_attr'];
							unset ($current[$tag . '_attr']);
						}
					}
					$last_item_index = $repeated_tag_index[$tag . '_' . $level] - 1;
					$current = & $current[$tag][$last_item_index];
				}
			}
			elseif ($type == "complete") { //Tags that ends in 1 line '<tag />'
				//See if the key is already taken.
				if (!isset ($current[$tag])) { //New Key
					$current[$tag] = $result;
					$repeated_tag_index[$tag . '_' . $level] = 1;
					if ($priority == 'tag' and $attributes_data)
						$current[$tag . '_attr'] = $attributes_data;
				} else { //If taken, put all things inside a list(array)
					if (isset ($current[$tag][0]) and is_array($current[$tag])) {
						//If it is already an array...
						// ...push the new element into that array.
						$current[$tag][$repeated_tag_index[$tag . '_' . $level]] = $result;

						if ($priority == 'tag' and $get_attributes and $attributes_data) {
							$current[$tag][$repeated_tag_index[$tag . '_' . $level] . '_attr'] = $attributes_data;
						}
						$repeated_tag_index[$tag . '_' . $level]++;
					} else { //If it is not an array...
						$current[$tag] = array (
							$current[$tag],
							$result
						); //...Make it an array using using the existing value and the new value
						$repeated_tag_index[$tag . '_' . $level] = 1;
						if ($priority == 'tag' and $get_attributes) {
							if (isset ($current[$tag . '_attr'])) { //The attribute of the last(0th) tag must be moved as well
								$current[$tag]['0_attr'] = $current[$tag . '_attr'];
								unset ($current[$tag . '_attr']);
							}

							if ($attributes_data) {
								$current[$tag][$repeated_tag_index[$tag . '_' . $level] . '_attr'] = $attributes_data;
							}
						}
						$repeated_tag_index[$tag . '_' . $level]++; //0 and 1 index is already taken
					}
				}
			}
			elseif ($type == 'close') { //End of tag '</tag>'
				$current = & $parent[$level -1];
			}
		}

		return ($xml_array);
	}

	/**
	 * 数组转XML
	 * @param array $array 数组
     * @param string $rootName 根节点名称，默认DATA
	 * @param string $encoding 字符集合 默认 UTF-8
	 * @return XML 
	 */
	public static function array2xml($array, $rootName='DATA', $encoding = 'utf-8') {
		$xml = '<?xml version="1.0" encoding="' . $encoding . '"?>';
		$xml .= '<'.strtoupper($rootName).'>';
		$xml .= self :: _array2xml($array);
		$xml .= '</'.strtoupper($rootName).'>';
		return $xml;
	}
    /**
	 * 递归数组转XML
	 * @param array $array 数组
	 * @return XML 
	 */
	public static function _array2xml($array) {
		$xml = "";
		foreach ($array as $key => $val) {
			$keyName = $key;
			if (is_numeric($key)) {
				$keyName = "K".$key;
			}
			$xml .= "<$keyName>";
			$tmp_val = is_array($val) ? self :: _array2xml($val) : self :: _cdata($val);
			$xml .= $tmp_val=="" ? "null" : $tmp_val;
			list ($keyName,) = explode(' ', $keyName);
			$xml .= "</$keyName>";
		}
		return $xml;
	}
    /** 加入 <![CDATA[ ]]> */
	public static function _cdata($str) {
		$pos = strpos($str, '<');
		if ($pos !== false)
			$str = '<![CDATA[' . $str . ']]>';
		return $str;
	}
//==========================================================================================================
}
?>
